package core;

import java.net.*;
import java.io.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

import core.ISpeaker;

//TODO: make some threads daemons

class Listener extends Thread
{
    ServerSocket slot;
    public Listener(ServerSocket _slot)
    {
        slot = _slot;
    }
    public void run()
    {
        try
        {
            while (!slot.isClosed())
            {
                Socket socket = slot.accept();
                Client.instance().newConnection(socket);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

public class Client extends Thread
{
//TODO: thread-safety
    private int port = 1010;
    private HashMap<Contact, Socket> sockets;
    private static Client c;
	private static Object locker = new Object();
    private boolean started;
    private ServerSocket slot;
	ISpeaker speaker;
    
    private Client()
    {
        port = Settings.instance().getPort();
        sockets = new HashMap<Contact, Socket>();
        started = false;
    }
    
	public void setSpeaker(ISpeaker speaker)
	{
		this.speaker = speaker;
	}
	
    public static Client instance()
    {
        if (c == null)
			synchronized(locker)
			{
				if (c == null)
					c = new Client();
			}
            
        return c;
    }
    
    public void run()
    {
        try
        {
            started = true;
            slot = new ServerSocket(port);
            Listener l = new Listener(slot);
			l.setDaemon(true);
            l.start();
            while (started)
            {
                Set<Contact> contacts;
                synchronized (sockets) 
                {
                    contacts = sockets.keySet();
                }
                Iterator<Contact> iter = contacts.iterator();
                while (iter.hasNext())
                {
					Contact c = (Contact)iter.next();
                    Socket socket = sockets.get(c);
                    if (socket.getInputStream().available() > 0)
                        incoming(c, socket);
                }
            }
            slot.close();
        }
        catch (Exception e)
        {
            try
            {
                slot.close();
            }
            catch (Exception x)
            {
                e.printStackTrace();
            }
            e.printStackTrace();
        }
    }
    
    public void newConnection(Socket incoming)
    {
        synchronized (sockets)
        {
            try
            {
                //System.out.println("New connection");
                DataOutputStream out = new DataOutputStream(incoming.getOutputStream());
                DataInputStream in = new DataInputStream(incoming.getInputStream());
                InetAddress address = incoming.getInetAddress();
                int remotePort = in.readInt();
                //System.out.format("client: %s:%d\n", address.getHostAddress(), remotePort);
                if ((remotePort >=0 ) && (remotePort <= 65535))
                {
                    Contact contact = new Contact(address.getHostAddress(), remotePort);
                    Socket s = sockets.put(contact, incoming);
                    if (s != null)
                        if (!s.isClosed())
                            s.close();
                }
                else
                    incoming.close();
            }
            catch (Exception e)
            {
                try
                {
                    incoming.close();
                }
                catch (Exception x)
                {
                    e.printStackTrace();
                }
                e.printStackTrace();
            }
        }
    }
    
    protected void incoming(Contact contact, Socket incoming) throws java.io.IOException
    {
        DataInputStream in = new DataInputStream(incoming.getInputStream());
        String message = in.readUTF();
        speaker.messageRecived(contact, message);
    }
    
    public void send(Contact contact, String message)
    {
        establishConnection(contact);
        try
        {
            Socket dest = sockets.get(contact);
            synchronized (dest)
            {
                DataOutputStream out = new DataOutputStream(dest.getOutputStream());
                out.writeUTF(message);
                
                out.flush();
            }
        }
        catch (Exception e)
        {
        
        }
    }
    
    public void establishConnection(Contact contact)
    {
		//System.out.format("Establish connection\n");
        synchronized (sockets)
        {
            if (sockets.containsKey(contact))
			{
				//System.out.format("Contains key!\n");
                if (!sockets.get(contact).isClosed())
                    return;
			}

            try
            {
                Socket socket = new Socket(contact.address, contact.port);
                DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                out.writeInt(Settings.instance().getPort());
                out.flush();
                if (!socket.isClosed())
                {
                    sockets.put(contact, socket);
                }
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}